package br.org.bertol.mestrado.engine.optimisation.moo;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

import org.apache.log4j.Logger;

import br.org.bertol.mestrado.Verify;
import br.org.bertol.mestrado.engine.Objective;
import br.org.bertol.mestrado.engine.optimisation.ParetoRepository;
import br.org.bertol.mestrado.engine.optimisation.Unit;

public class AdaptativeGrid<U extends Unit> {

    private final ArrayList<U>               archive;

    private final ArrayList<U>               notAllowedPareto;

    private final ArrayList<Double>          gridPlacesChances;

    private final ArrayList<Integer>         gridPlacesOccupiedPlaces;

    private final double[]                   gridPopulation;

    private final Verify                     verify;

    private final EnumMap<Objective, Double> globalLowerLimit;

    private final EnumMap<Objective, Double> globalUpperLimit;

    private final ParetoRepository<U>        paretoRepository;

    private final int                        DIVISIONS;

    private final int                        MAX_GRID_SIZE;

    private final int                        MAX_OBJECTIVES;

    private final int                        REPOSITORY_SIZE;

    private float                            gridChancesSum;

    private final int                        SIZE_NOT_ALLOWED_REP;

    public AdaptativeGrid(final Verify verify, final int divisons,
        final int repositorySize, final ParetoRepository<U> paretoRepository) {

        SIZE_NOT_ALLOWED_REP = 1000;

        archive = new ArrayList<U>(0);

        notAllowedPareto = new ArrayList<U>(SIZE_NOT_ALLOWED_REP);

        notAllowedPareto.ensureCapacity(SIZE_NOT_ALLOWED_REP);

        MAX_OBJECTIVES = verify.getObjectives().length;

        DIVISIONS = divisons;

        MAX_GRID_SIZE = Double.valueOf(Math.pow(DIVISIONS, MAX_OBJECTIVES))
                .intValue();

        REPOSITORY_SIZE = repositorySize;

        globalLowerLimit = new EnumMap<Objective, Double>(Objective.class);

        globalUpperLimit = new EnumMap<Objective, Double>(Objective.class);

        for (Objective objective : verify.getObjectives()) {
            globalLowerLimit.put(objective, 0d);

            globalUpperLimit.put(objective, 0d);
        }

        // sempre mais quadrados que o n�mero q ser� c�lculado para evitar out
        // of bounds
        gridPopulation = new double[MAX_GRID_SIZE];

        this.verify = verify;

        gridChancesSum = 0f;

        gridPlacesChances = new ArrayList<Double>();

        gridPlacesOccupiedPlaces = new ArrayList<Integer>();

        this.paretoRepository = paretoRepository;
    }

    @SuppressWarnings("unchecked")
    public void startGrid(final List<U> list) throws Exception {

        final ArrayList<U> start = new ArrayList<U>(list);

        Collections.sort(start, new OrdenatePareto<U>());

        final ArrayList<U> tmp = new ArrayList<U>();

        for (U u : start) {
            tmp.add((U) u.clone());
        }

        tmp.trimToSize();

        archive.addAll(tmp.subList(0, (tmp.size() < REPOSITORY_SIZE ? tmp
                .size() : REPOSITORY_SIZE)));

        updateGridPosition();
    }

    /**
     * @return the archive
     */
    @SuppressWarnings("unchecked")
    public ArrayList<U> getGrid() {
        return (ArrayList<U>) archive.clone();
    }

    /**
     * @return the gridPopulation
     */
    public double[] getGridPopulation() {
        return gridPopulation;
    }

    @SuppressWarnings("unchecked")
    public U getUnit() throws CloneNotSupportedException {

        if (archive.size() > 0) {

            if (archive.size() == 1)
                return (U) archive.get(0).clone();

            double partialChance = 0;

            int i = 0, gridposition = 0;

            final Random random = new Random(System.currentTimeMillis());

            final double randomChance = random.nextDouble() * gridChancesSum;

            for (i = 0; i < gridPlacesOccupiedPlaces.size()
                    && partialChance < randomChance; i++) {
                partialChance += gridPlacesChances.get(i);
                gridposition = gridPlacesOccupiedPlaces.get(i);
            }

            final List<U> seletetd_squares = new ArrayList<U>();

            for (U particle : archive) {
                if (particle.getSpacialPosition() == gridposition)
                    seletetd_squares.add(particle);
            }

            int randPArticule = (int) Math.floor(seletetd_squares.size()
                    * random.nextDouble());

            return seletetd_squares.get(randPArticule);
        }

        return null;
    }

    public void updateGridStatus() {

        // zera todos os contadores
        gridPlacesChances.clear();

        gridPlacesChances.trimToSize();

        gridPlacesOccupiedPlaces.clear();

        gridPlacesOccupiedPlaces.trimToSize();

        gridChancesSum = 0f;

        // realiza contagem dos corpos
        for (int i = 0; i < gridPopulation.length; i++) {

            if (gridPopulation[i] > 0d) {
                gridPlacesChances.add(1.0 / gridPopulation[i]);
                gridPlacesOccupiedPlaces.add(i);
                gridChancesSum += 1.0 / gridPopulation[i];
            }
        }

    }

    /**
     * Realiza verifica��o de pareto para atualiza��o do local best de uma
     * part�cula.
     * @param particle
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public void updateGrid(final List<U> particleList) throws Exception {

        for (U unit : particleList) {

            // verifica se o movimento da part�cula � v�lido

            // verifica��o para mono objetivo
            if (verify.getObjectives().length == 1) {

                U gbest = (U) archive.get(0).clone();

                if (unit.getHeight().get(verify.getObjectives()[0]) < gbest
                        .getHeight().get(verify.getObjectives()[0])) {

                    Logger.getLogger(getClass())
                            .debug(
                                   "Melhor GBest encontrado.");

                    Logger.getLogger(getClass()).debug(
                                                       "GBest anterior: "
                            + gbest);

                    archive.set(0, (U) unit.clone());

                    Logger.getLogger(getClass())
                            .debug(
                                   "GBest    atual: " + archive.get(0));
                }
            } else {

                final int pareto_dominante = paretoRepository
                        .verifyDominance(unit);

                if (pareto_dominante != ParetoDominace.RIGHT_DOMINE) {

                    // caso seja dominante
                    if (pareto_dominante == ParetoDominace.LEFT_DOMINE
                            || archive.size() < REPOSITORY_SIZE) {
                        addGridUnit(unit);
                    }
                    // caso n�o tenha domin�ncia e arquivo esteja full
                    // usar uma posi��o do hiper cubo menos populada
                    // substitui unidade
                    else {
                        replaceGridUnit(unit);
                    }

                    archive.trimToSize();
                }
            }

            /*
             * if(!notAllowedPareto.contains(unit)) // adiciona unida
             * notAllowedPareto.add((U) unit.clone());
             */
        }
        notAllowedPareto.trimToSize();
    }

    @SuppressWarnings("unchecked")
    private void addGridUnit(final U unit) throws Exception {

        Logger.getLogger(getClass()).debug("#####In�cio da opera��o.#####");

        // procura posi��o no cubo
        // para part�cula
        // realiza verifica��o no candidato
        // final U new_unit = applyILS(unit);
        final U new_unit = (U) unit.clone();

        // verifyDominance(new_unit);

        int square = findGridUnitLocation(new_unit.getHeight());

        archive.add(new_unit);

        new_unit.setSpacialPosition(square - 1);

        // caso diferente de zero, marca
        if (square != 0)
            gridPopulation[new_unit.getSpacialPosition()]++;
        // sen�o atualiza todas as posi��es
        else
            updateGridPosition();

        archive.trimToSize();

        Logger.getLogger(getClass())
                .info(
                      "Nova unidade adicionada. Sum�rio da opera��o ---- : "
                + new_unit);

        Logger.getLogger(getClass())
                .debug(
                       "Ocupa��o atual do reposit�rio ------------------ : "
                + archive.size());

        Logger.getLogger(getClass()).debug("#####Fim da opera��o.#####");

    }

    @SuppressWarnings("unchecked")
    private void replaceGridUnit(final U unit) throws Exception {

        Logger.getLogger(getClass()).debug("#####In�cio da opera��o.#####");

        Logger.getLogger(getClass()).debug("Substituindo part�cula.");

        // realiza verifica��o no candidato

        // final U new_unit = applyILS(unit);

        final U new_unit = (U) unit.clone();

        // verifyDominance(new_unit);

        int square = findGridUnitLocation(new_unit.getHeight());

        int maxPopulation = searchMaxPopulated();

        Logger.getLogger(getClass())
                .debug(
                       "Quadrante mais ocupada: " + maxPopulation
                + ". Popula��o: "
                + gridPopulation[maxPopulation]);

        // caso n�o tenha lugar no grid
        // remove uma part�cula da posi��o mais populosa
        // e adiciona a nova l�
        if (square == 0) {

            U erased = null;

            for (U p : archive) {
                if (p.getSpacialPosition() == maxPopulation) {
                    erased = p;
                    break;
                }
            }
            // apaga item
            archive.remove(erased);

            Logger.getLogger(getClass())
                    .debug(
                           "Part�cula removida: Sum�rio da opera��o ------ : "
                    + erased);

            // adiciona novo candidato
            archive.add(new_unit);
            // atualiza grid
            updateGridPosition();

            Logger.getLogger(getClass())
                    .debug(
                           "Ocupa��o atual do reposit�rio ---------------- : "
                    + archive.size());
        }
        // caso a posi��o da escolhida da part�cula tenha
        // uma popula��o menor, adiciona a part�cula nova
        // e transfere uma de outra posi��o, sen�o descarta
        else if (gridPopulation[square - 1] < gridPopulation[maxPopulation]) {

            U erased = null;

            for (U p : archive) {
                if (p.getSpacialPosition() == maxPopulation) {
                    erased = p;
                    break;
                }
            }
            // apaga item
            archive.remove(erased);
            gridPopulation[erased.getSpacialPosition()]--;
            Logger
                    .getLogger(getClass())
                    .debug(
                           "Popula��o do quadrante "
                    + erased.getSpacialPosition()
                    + " : "
                    + gridPopulation[erased.getSpacialPosition()]);

            Logger.getLogger(getClass())
                    .debug(
                           "Part�cula removida: Sum�rio da opera��o ---- : "
                    + erased);

            // adiciona novo candidato
            archive.add(new_unit);
            new_unit.setSpacialPosition(square - 1);
            gridPopulation[new_unit.getSpacialPosition()]++;
            Logger.getLogger(getClass())
                    .debug(
                           "Popula��o do quadrante "
                    + new_unit.getSpacialPosition()
                    + " : "
                    + gridPopulation[new_unit
                    .getSpacialPosition()]);

            Logger.getLogger(getClass())
                    .info(
                          "Nova unidade adicionada. Sum�rio da opera��o -- : "
                    + new_unit);

            Logger.getLogger(getClass())
                    .debug(
                           "Ocupa��o atual do reposit�rio ---------------- : "
                    + archive.size());

        } else
            Logger.getLogger(getClass())
                    .debug(
                           "Part�cula descartada ----------------------- : "
                    + new_unit);

        Logger.getLogger(getClass()).debug("#####Fim da opera��o.#####");
    }

    private void updateGridPosition() {

        final EnumMap<Objective, Double> local_lim_inf = new EnumMap<Objective, Double>(
                Objective.class);

        final EnumMap<Objective, Double> local_sup_lim = new EnumMap<Objective, Double>(
                Objective.class);

        // inicia arrays auxiliares
        for (Objective objective : verify.getObjectives()) {
            local_lim_inf.put(objective, 1000000d);

            local_sup_lim.put(objective, -1000000d);
        }

        // procura o maior e menor f(x) dentro os elementos do arquivo
        for (Objective objective : verify.getObjectives()) {
            for (U nd_partice : archive) {
                // menor objetivo vai para offset
                if (nd_partice.getHeight().get(objective).intValue() < local_lim_inf
                        .get(objective).doubleValue())
                    local_lim_inf.put(objective, nd_partice.getHeight()
                            .get(
                                 objective).doubleValue());
                // maior objetivo vai para largest
                if (nd_partice.getHeight().get(objective).intValue() > local_sup_lim
                        .get(objective).doubleValue())
                    local_sup_lim.put(objective, nd_partice.getHeight()
                            .get(
                                 objective).doubleValue());
            }
        }

        for (Objective objective : verify.getObjectives()) {
            globalLowerLimit
                    .put(
                         objective,
                         (local_sup_lim.get(objective).doubleValue() - local_lim_inf
                                 .get(objective).doubleValue())
                                 / (DIVISIONS - 1));

            globalUpperLimit.put(objective, local_lim_inf.get(objective)
                    .doubleValue()
                    - (globalLowerLimit.get(objective).doubleValue() / 2.0));
        }

        for (int square = 0; square < MAX_GRID_SIZE; square++)
            gridPopulation[square] = 0;

        // reposiciona todas as part�culas
        for (U paretoFrontItem : archive) {
            int square = findGridUnitLocation(paretoFrontItem.getHeight()) - 1;
            paretoFrontItem.setSpacialPosition(square);
            gridPopulation[paretoFrontItem.getSpacialPosition()]++;
        }
    }

    /**
     * Encontra a posi��o espacial de uma determinada part�cula de acordo com os
     * valores das fun��es objetivos
     * @param objectiveMap
     * @return
     */
    private int findGridUnitLocation(
            final EnumMap<Objective, Integer> objectiveMap) {

        // localiza��o de part�cula
        int location = 0, dim = MAX_OBJECTIVES - 1;

        // verifica se as solu��o para cada objetivo pesquisado est�o
        // dentro do grid
        for (Iterator<Objective> iterator = objectiveMap.keySet().iterator(); iterator
                .hasNext(); dim--) {
            Objective objective = iterator.next();

            int temp = (int) (Math.floor((objectiveMap.get(objective)
                    .doubleValue() - globalUpperLimit.get(objective)
                    .doubleValue())
                    / globalUpperLimit.get(objective).doubleValue()));

            if (temp > DIVISIONS || temp < 0)
                return 0;

            location += Math.floor(temp * Math.pow(DIVISIONS, dim));

        }
        return location + 1;
    }

    private int searchMaxPopulated() {
        int i, fmax = 0;

        for (i = 1; i < MAX_GRID_SIZE; i++)
            if (gridPopulation[fmax] < gridPopulation[i])
                fmax = i;

        return fmax;
    }
}
